(function ($) {
    $.fbMultiShare = function (options) {
        // user-defined parameters
        var options = $.extend({
			friendContainer: 'ul#friends' // (DOM element) container for list of friends
            , friendElement: 'li' // (DOM element) element id must equal friend user id
            , selectedClass: 'fbms-selected' // (string) class name used for selected friend element
            , serverPost: false // (boolean) 
            , serverPostParams: null // (object/string) parameters to be sent to server for server-side post
            , serverCallbackPath: '' // (string) callback path for server-side post
            , searchBox: '#fbms-search' // (DOM element) 
            , messageBox: '#fbms-message' // (DOM element) 
            , submitButton: '.fbms-submit' // (DOM element) 
            , submitButtonClick: null // (callback) 
            , submitButtonSuccess: null // (callback) 
            , submitButtonError: null // (callback) 
            , cancelButton: '.fbms-cancel' // (DOM element) 
            , cancelButtonClick: null // (callback) 
            , error: null // (callback) 
            , notificationsElement: '#fbms-notifications' // (DOM element) element for displaying "notificationsHtml" if a friend is not selected (can be used by other callbacks)
            , notificationsHtml: 'Please select a friend.' // (string) message that gets appended to "notificationsElement" if a friend is not selected (can be used by other callbacks)

            // wall post parameters
            , wpMessage: ''
            , wpPicture: ''
            , wpLinkURL: ''
            , wpLinkText: ''
            , wpCaption: '&nbsp;'
            , wpDescription: ''
			, wpSource: ''
			, wpActions: ''
			, wpPrivacy: ''
			, wpTargeting: ''
        }, options || {});

        // configure plugin
        fbMultiShare = function() {
            this.init(); // constructor call
        };

        fbMultiShare.prototype = {
            // constructor
            init: function()
            {
				// DOM element cache
				_this = this;
				this.container = $(options.friendContainer);
				this.friendList = this.container.children(options.friendElement);
				this.searchBox = $(options.searchBox);
				this.searchBoxDefaultValue = this.searchBox.val();
				this.messageBox = $(options.messageBox);
				this.submitButton = $(options.submitButton);
				this.cancelButton = $(options.cancelButton);
				this.notificationsElement = $(options.notificationsElement);

				// setup methods
                this.attachEvents();
            },

			// bind custom events to actions
            attachEvents: function()
            {
				this.friendList.bind('click', function(event) { _this.onFriendClick(event); })
                this.submitButton.bind('click', function(event) { _this.onSubmitButtonClick(event); });
                this.cancelButton.bind('click', function(event) { _this.onCancelButtonClick(event); });
				this.searchBox.bind('keyup', function(event) { _this.onSearch(event); })
					.bind('focus', function(event) { _this.onTextFieldFocus(event); })
					.bind('blur', function(event) { _this.onTextFieldBlur(event); });
				this.messageBox.bind('focus', function(event) { _this.onTextFieldFocus(event); })
					.bind('blur', function(event) { _this.onTextFieldBlur(event); });
			},

			// friend is selected
            onFriendClick: function(event)
            {
                event.preventDefault();
				
				$('.' + options.selectedClass, this.friendsList).removeClass(options.selectedClass);
				$(event.target).closest(options.friendElement).addClass(options.selectedClass);
			},

            // post message on wall of all selected friends and redirect to user-defined location
            onSubmitButtonClick: function(event)
            {
                event.preventDefault();
				
				this.selectedId = $('.' + options.selectedClass, this.friendsList).attr('id');
				
				// show error message if a friend has not been selected
				if (this.selectedId == null) {
					if (this.notificationsElement.length) {
						this.notificationsElement.show();
					}

					if ($.isFunction(options.error)) {
						options.error.call(this, options);
					}

					this.notificationsElement.html(options.notificationsHtml);
					return;
				}

				// hide error message if a friend has been selected
				if (this.notificationsElement.length) {
					this.notificationsElement.hide();
					this.notificationsElement.empty();
				}

                // execute custom method on submit
                if ($.isFunction(options.submitButtonClick)) {
                    options.submitButtonClick.call(this, options);
				}

				// execute default method on submit
                if (options.serverPost) {
                    this.postServerSide.call(this, options);
                } else {
                    this.postClientSide.call(this, options);
                };
            },

			// callback for cancel button
			onCancelButtonClick: function (data) {
				$('.' + options.selectedClass).removeClass(options.selectedClass); // remove friend selection
				this.searchBox.val(this.searchBoxDefaultValue); // reset search field value
				this.friendList.filter(':hidden').show(); // show entire friends list

				if ($.isFunction(options.cancelButtonClick)) {
					options.cancelButtonClick.call(this, options, data);
				}
			},

            // execute server-side wall post if defined
			postServerSide: function(options)
			{
				$.ajax({
					type: 'POST',
					url: options.serverCallbackPath,
					data: options.serverPostParams,
					success: function (response) { _this.onSubmitButtonSuccess(response); },
					error: function (response) { _this.onSubmitButtonError(response); }
				});
			},

            // execute client-side wall post using Facebook API
			postClientSide: function(options)
			{
				var wallParams = {
					message: (this.messageBox.val() == this.messageBox[0].defaultValue ? (options.wpMessage == '' ? '' : options.wpMessage) : this.messageBox.val())
					, picture: (options.wpPicture == '' ? '' : options.wpPicture)
					, link: options.wpLinkURL
					, name: options.wpLinkText
					, caption: options.wpCaption
					, description: options.wpDescription
					, source: options.wpSource
					, actions: options.wpActions
					, privacy: options.wpPrivacy
					, targeting: options.wpTargeting
				}

                FB.api(this.selectedId + '/feed', 'post', wallParams, function(response) {
					if (!response || response.error) {
						_this.onSubmitButtonError(response);
					} else {
						_this.onSubmitButtonSuccess(response);
					}
				});
			},

			// callback for wall post success
			onSubmitButtonSuccess: function (response) {
				if ($.isFunction(options.submitButtonSuccess)) {
					options.submitButtonSuccess.call(this, options, response);
				}
			},

			// callback for wall post error
			onSubmitButtonError: function (response) {
				if ($.isFunction(options.submitButtonError)) {
					options.submitButtonError.call(this, options, response);
				}
			},

            // key strokes for search box
			onSearch: function(event)
			{
				var _this = $(event.target);
				var val = (_this.val() == null ? '' : _this.val());
				var lastVal = (_this.data('lastVal') == null ? '' : _this.data('lastVal'));
				_this.data('lastVal', val);

				// if the filter is empty, reset the list
				if (val == '') {
					this.friendList.show();
					return;
				}

				// if the filter hasn't changed, do nothing
				if (val == lastVal) { return; }

				// hide the results of the previous filter
				this.friendList.filter(':visible').hide();

				// custom pseudo-selector that selects elements whose text contains the specified substring (it is case-insensitive, unlike the built-in :contains selector)
				$.extend($.expr[':'], {
					icontains: function (elem, i, match) {
						return (new RegExp(match[3], 'im')).test($(elem).text());
					}
				});

				// show only friends that match the filter
				this.friendList.filter(':icontains(' + val + ')').show();
			},

            // hide placeholder text in search box
			onTextFieldFocus: function(event)
			{
				if (event.target.value == event.target.defaultValue) {
					event.target.value = '';
				}
			},

            // show placeholder text in search box
			onTextFieldBlur: function(event)
			{
				if (event.target.value == '') {
					event.target.value = event.target.defaultValue;
				}
			}
		};

        // call plugin methods
        new fbMultiShare(options);
    };
})(jQuery);
